#include "sbrush_instrument.h"

#include <string.h>
#include <string>
#include <map>
#include <memory>
#include <iostream>

using namespace std;
using namespace sbrush;
using namespace dag;

namespace {
	template<typename _type>
	void safe_copy(_type* dst, const _type* src, size_t len) {
		if (len > 0) {
			memcpy(dst, src, len * sizeof(_type));
		}
	}
	
	template<typename _type>
	const _type& get_value_for_key(const map<string, _type>& keyMap,
								   const string& key) {
		typename map<string, _type>::const_iterator iter;
		iter = keyMap.find(key);
		if (iter == keyMap.end()) {
			throw KeyError(key.c_str());
		}
		return iter->second;
	}
	
	void write_string(const string& str, ostream& binaryStream) {
		unsigned count = str.size();
		binaryStream.write((const char*)&count, sizeof(count));
		if (count > 0) {
			binaryStream.write(str.c_str(), count);
		}
	}
	
	void read_string(string& str, istream& binaryStream) {
		unsigned count = 0;
		str = "";
		binaryStream.read((char*)&count, sizeof(count));
		while (count > 0) {
			char letter;
			binaryStream.read(&letter, sizeof(letter));
			str += letter;
			count--;
		}
	}
	
	template<typename _meta_t>
	void write_meta(const map<string, _meta_t>& meta, 
					ostream& binaryStream) {
		unsigned count = meta.size();
		binaryStream.write((const char*)&count, sizeof(count));
		typename map<string, _meta_t>::const_iterator metaIter;
		for (metaIter = meta.begin(); metaIter != meta.end(); metaIter++) {
			write_string(metaIter->first, binaryStream);
			binaryStream.write((const char*)&(metaIter->second), 
							   sizeof(_meta_t));
		}
	}
	
	template <>
	void write_meta<string>(const map<string, string>& meta, 
							ostream& binaryStream) {
		unsigned count = meta.size();
		binaryStream.write((const char*)&count, sizeof(count));
		map<string, string>::const_iterator metaIter;
		for (metaIter = meta.begin(); metaIter != meta.end(); metaIter++) {
			write_string(metaIter->first, binaryStream);
			write_string(metaIter->second, binaryStream);
		}
	}
	
	template<typename _meta_t>
	void read_meta(map<string, _meta_t>& meta,
				   istream& binaryStream) {
		meta.clear();
		unsigned count = 0;
		binaryStream.read((char*)&count, sizeof(count));
		while (count > 0) {
			string key;
			read_string(key, binaryStream);
			_meta_t val;
			binaryStream.read((char*)&val, sizeof(_meta_t));
			meta[key] = val;
			count--;
		}
	}
		
	template <>
	void read_meta<string>(map<string, string>& meta,
						   istream& binaryStream) {
		meta.clear();
		unsigned count = 0;
		binaryStream.read((char*)&count, sizeof(count));
		while (count > 0) {
			string key;
			read_string(key, binaryStream);
			string val;
			read_string(val, binaryStream);
			meta[key] = val;
			count--;
		}
	}
}


KeyError::KeyError(const char* invalidKey)
:	m_Key(invalidKey),
AcsError("key error")
{
}

KeyError::~KeyError() throw() {
}

const char* KeyError::key() const {
	return m_Key.c_str();
}

InstrumentState::InstrumentState()
:	m_BSize(0),
m_ISize(0),
m_FSize(0),
m_DSize(0),
m_BData(NULL),
m_IData(NULL),
m_FData(NULL),
m_DData(NULL)
{
}

InstrumentState::InstrumentState(const InstrumentState& copy) 
:	m_BSize(0),
m_ISize(0),
m_FSize(0),
m_DSize(0),
m_BData(NULL),
m_IData(NULL),
m_FData(NULL),
m_DData(NULL)
{
	*this = copy;
}

InstrumentState::~InstrumentState() throw() {
	delete[] m_BData;
	delete[] m_IData;
	delete[] m_FData;
	delete[] m_DData;
}

InstrumentState& InstrumentState::operator=(const InstrumentState& rhs) {
	if (this == &rhs) {
		return *this;
	}
	
	resize(rhs.boolSize(), rhs.intSize(), rhs.floatSize(), rhs.doubleSize());
	safe_copy<bool>(m_BData, rhs.bools(), rhs.boolSize());
	safe_copy<int>(m_IData, rhs.ints(), rhs.intSize());
	safe_copy<float>(m_FData, rhs.floats(), rhs.floatSize());
	safe_copy<double>(m_DData, rhs.doubles(), rhs.doubleSize());
	
	return *this;
}

void InstrumentState::set(const bool* b, size_t bSize,
						  const int* i, size_t iSize,
						  const float* f, size_t fSize,
						  const double* d, size_t dSize) {
	resize(bSize, iSize, fSize, dSize);
	safe_copy<bool>(m_BData, b, bSize);
	safe_copy<int>(m_IData, i, iSize);
	safe_copy<float>(m_FData, f, fSize);
	safe_copy<double>(m_DData, d, dSize);
}

size_t InstrumentState::boolSize() const {
	return m_BSize;
}

size_t InstrumentState::intSize() const {
	return m_ISize;
}

size_t InstrumentState::floatSize() const {
	return m_FSize;
}

size_t InstrumentState::doubleSize() const {
	return m_DSize;
}

const bool* InstrumentState::bools() const {
	return m_BData;
}

const int* InstrumentState::ints() const {
	return m_IData;
}

const float* InstrumentState::floats() const {
	return m_FData;
}

const double* InstrumentState::doubles() const {
	return m_DData;
}

bool* InstrumentState::bools() {
	return m_BData;
}

int* InstrumentState::ints() {
	return m_IData;
}

float* InstrumentState::floats() {
	return m_FData;
}

double* InstrumentState::doubles() {
	return m_DData;
}


int InstrumentState::metaInt(const string& key) const {
	return get_value_for_key<int>(m_MetaInts, key);
}

void InstrumentState::setMetaInt(const string& key, int val) {
	m_MetaInts[key] = val;
}

double InstrumentState::metaDouble(const string& key) const {
	return get_value_for_key<double>(m_MetaDoubles, key);
}

void InstrumentState::setMetaDouble(const string& key, double val) {
	m_MetaDoubles[key] = val;
}

const string& InstrumentState::metaString(const string& key) const {
	return get_value_for_key<string>(m_MetaStrings, key);
}

void InstrumentState::setMetaString(const string& key, const string& val) {
	m_MetaStrings[key] = val;
}

void InstrumentState::resize(size_t bSize, 
							 size_t iSize, 
							 size_t fSize, 
							 size_t dSize) {
	try {		
		if (m_BSize != bSize) {
			delete[] m_BData;
			m_BData = NULL;
			if (bSize > 0) {
				m_BSize = bSize;
				m_BData = new bool[m_BSize];
			}
		}
		
		if (m_ISize != iSize) {
			delete[] m_IData;
			m_IData = NULL;
			if (iSize > 0) {
				m_ISize = iSize;
				m_IData = new int[m_ISize];
			}
		}
		
		if (m_FSize != fSize) {
			delete[] m_FData;
			m_FData = NULL;
			if (fSize > 0) {
				m_FSize = fSize;
				m_FData = new float[m_FSize];
			}
		}
		
		if (m_DSize != dSize) {
			delete[] m_DData;
			m_DData = NULL;
			if (dSize > 0) {
				m_DSize = dSize;
				m_DData = new double[m_DSize];
			}
		}
	}
	catch (...) {
		delete[] m_BData;
		m_BData = NULL;
		delete[] m_IData;
		m_IData = NULL;
		delete[] m_FData;
		m_FData = NULL;
		delete[] m_DData;
		m_DData = NULL;
		m_BSize = 0;
		m_ISize = 0;
		m_FSize = 0;
		m_DSize = 0;
		
		throw;
	}
}

void InstrumentState::toFrame(Frame& frame) const {
	frame.resize(FrameSpec(m_BSize, m_ISize, m_FSize, m_DSize));
	if (m_BSize > 0) {
		memcpy(frame.bools, m_BData, sizeof(bool) * m_BSize);
	}
	if (m_ISize > 0) {
		memcpy(frame.ints, m_IData, sizeof(int) * m_ISize);
	}
	if (m_FSize > 0) {
		memcpy(frame.floats, m_FData, sizeof(float) * m_FSize);
	}
	if (m_DSize > 0) {
		memcpy(frame.doubles, m_DData, sizeof(double) * m_DSize);
	}
}


void InstrumentState::serialize(ostream& binaryStream) const {
	/* utilize unsigned rather than size_t because unsigned more likely to be
	 * the same across different architectures */
	unsigned count = m_BSize;
	binaryStream.write((const char*)&count, sizeof(count));
	count = m_ISize;
	binaryStream.write((const char*)&count, sizeof(count));
	count = m_FSize;
	binaryStream.write((const char*)&count, sizeof(count));
	count = m_DSize;
	binaryStream.write((const char*)&count, sizeof(count));
	
	if (m_BSize > 0) {
		binaryStream.write((const char*)m_BData, sizeof(bool) * m_BSize);
	}
	if (m_ISize > 0) {
		binaryStream.write((const char*)m_IData, sizeof(int) * m_ISize);
	}
	if (m_FSize > 0) {
		binaryStream.write((const char*)m_FData, sizeof(float) * m_FSize);
	}
	if (m_DSize > 0) {
		binaryStream.write((const char*)m_DData, sizeof(double) * m_DSize);
	}
	
	write_meta(m_MetaInts, binaryStream);
	write_meta(m_MetaDoubles, binaryStream);
	write_meta(m_MetaStrings, binaryStream);
}

auto_ptr<InstrumentState> InstrumentState::inflate(istream& binaryStream) {
	auto_ptr<InstrumentState> state(new InstrumentState());
	unsigned bSize = 0;
	unsigned iSize = 0;
	unsigned fSize = 0;
	unsigned dSize = 0;
	
	binaryStream.read((char*)&bSize, sizeof(bSize));
	binaryStream.read((char*)&iSize, sizeof(iSize));
	binaryStream.read((char*)&fSize, sizeof(fSize));
	binaryStream.read((char*)&dSize, sizeof(dSize));
	state->resize(bSize, iSize, fSize, dSize);
	
	if (bSize > 0) {
		binaryStream.read((char*)state->bools(), sizeof(bool) * bSize);
	}
	if (iSize > 0) {
		binaryStream.read((char*)state->ints(), sizeof(int) * iSize);
	}
	if (fSize > 0) {
		binaryStream.read((char*)state->floats(), sizeof(float) * fSize);
	}
	if (dSize > 0) {
		binaryStream.read((char*)state->doubles(), sizeof(double) * dSize);
	}
	
	read_meta(state->m_MetaInts, binaryStream);
	read_meta(state->m_MetaDoubles, binaryStream);
	read_meta(state->m_MetaStrings, binaryStream);
	
	return state;
}
